﻿#region Header

// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------

#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using ScrumTable.Common.Collections;
using ScrumTable.Common.EventHandling;
using ScrumTable.Common.Logging;

#endregion


namespace ScrumTable.Common.Threading
{
    /// <summary>
    /// Provides a message queue which can be used to queue worker items and
    /// execute them in a single thread context.
    /// </summary>
    /// <typeparam name="TData">Type of the data argument objects to store.</typeparam>
    public class MessageQueue<TData> : DisposeBase
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        private const string EvtError = "OnError";

        private readonly Queue<QueueItem> _messageQueue = new Queue<QueueItem>();
        private readonly IDelegateHandlerCollection<ThreadingEventHandler> _events = new SyncDelegateHandlerCollection<ThreadingEventHandler>();

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        /// <summary>
        /// Fires an event if an exeption has occurred while processing an item.
        /// </summary>
        public event ThreadingEventHandler Error
        {
            add { Events.Add(EvtError, value); }
            remove { Events.Remove(EvtError, value); }
        }

        /// <summary>
        /// Gets the internal event handler list.
        /// </summary>
        protected IDelegateHandlerCollection<ThreadingEventHandler> Events
        {
            get { return _events; }
        }

        /// <summary>
        /// Returns true if the current message queue has been stopped.
        /// </summary>
        protected bool DoAbort
        {
            get;
            private set;
        }

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Creates a new MessageQueue instance.
        /// </summary>
        public MessageQueue()
        {
            Thread executor = new Thread(ProcessThread);
            executor.Name = GetType().Name;
            executor.IsBackground = true;
            executor.Start();
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Stops the message queue and shuts it down.
        /// </summary>
        public virtual void Abort()
        {
            lock (this)
            {
                DoAbort = true;
                Monitor.Pulse(this);
            }
        }

        /// <summary>
        /// Enqueues the callback using the specified Delegate which will
        /// be called on execution.
        /// </summary>
        /// <param name="toCall">Specifies the call executor.</param>
        /// <param name="argument">Data object which should be passed to the object to call.</param>
        public void Enqueue(ICall<TData> toCall, TData argument)
        {
            lock (this)
            {
                if (DoAbort)
                {
                    throw new InvalidOperationException("The queue was aborted, cannot queue items anymore.");
                }

                _messageQueue.Enqueue(new QueueItem { Call = toCall, Argument = argument });
                Monitor.Pulse(this);
            }
        }

        /// <summary>
        /// Releases managed (c# or .net) resources.
        /// </summary>
        protected override void ReleaseManagedResources()
        {
            Abort();
        }

        // -------------------------------
        // Queue processing procedures
        // -------------------------------

        private void ProcessThread()
        {
            while (!DoAbort)
            {
                ProcessMessages();
            }
        }

        private void ProcessMessages()
        {
            List<QueueItem> items = new List<QueueItem>();

            lock (this)
            {
                // wait till a message is available
                while (!DoAbort && _messageQueue.Count == 0)
                {
                    Monitor.Wait(this); ;
                }

                // process available messages
                while (!DoAbort && _messageQueue.Count > 0)
                {
                    items.Add(_messageQueue.Dequeue());
                }
            }
            items.ForEach(ExecuteItem);
        }

        /// <summary>
        /// Executes the queued item.
        /// </summary>
        /// <param name="itemToProcess">Internal QueueItem to process.</param>
        protected virtual void ExecuteItem(QueueItem itemToProcess)
        {
            try
            {
                itemToProcess.Execute();
            }
            catch (Exception error)
            {
                try
                {
                    Logger<MessageQueue<TData>>.Error(error);
                    OnError(new ThreadingErrorEventArgs(
                        new ThreadingException("Cannot execute the specified item.", error)));
                }
                catch (Exception logError)
                {
                    // ignore fatal errors and log them as fatal instead
                    Logger<MessageQueue<TData>>.Fatal(logError);
                }
            }
        }

        /// <summary>
        /// Fires the Error event.
        /// </summary>
        /// <param name="args">Event arguments which describe the error.</param>
        protected virtual void OnError(ThreadingErrorEventArgs args)
        {
            Events.Fire(EvtError, this, args);
        }

        #endregion

        // -------------------------------
        // Nested Inner Classes
        // -------------------------------

        /// <summary>
        /// Provides an entry in the message queue.
        /// </summary>
        protected class QueueItem
        {
            #region Properties
            //--------------------------------------------------------------------
            // Properties
            //--------------------------------------------------------------------

            /// <summary>
            /// Sepecifies the item to call.
            /// </summary>
            internal protected ICall<TData> Call { get; internal set; }

            /// <summary>
            /// Specifies the argument which will be passed to the item to call.
            /// </summary>
            internal protected TData Argument { get; internal set; }

            /// <summary>
            /// Executes the item to call with the given argument.
            /// </summary>
            internal void Execute() { Call.Execute(Argument); }

            #endregion
        }
    }
}
